/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#ifndef DEPENDENCY_UCOP_STORAGE_SQLSTORAGEIMPL_H_
#define DEPENDENCY_UCOP_STORAGE_SQLSTORAGEIMPL_H_

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif

#include "Ucop/Storage/SQLStorage.h"


namespace Ucop {
namespace Storage {

	template<class DerivedLoader, class StorageClass>
	template<class S, class D>
	void SQLStorageLoaderBase<DerivedLoader, StorageClass>::convert(std::uint32_t /*field_pos*/, S src, D& dst)
	{
		dst = D(src);
	}


	template<class DerivedLoader, class StorageClass>
	void SQLStorageLoaderBase<DerivedLoader, StorageClass>::convert_str_to_str(std::uint32_t /*field_pos*/, char const* src, char*& dst)
	{
		if (!src)
		{
			dst = new char[1];
			*dst = 0;
		}
		else
		{
			std::uint32_t l = strlen(src) + 1;
			dst = new char[l];
			memcpy(dst, src, l);
		}
	}


	template<class DerivedLoader, class StorageClass>
	template<class S>
	void SQLStorageLoaderBase<DerivedLoader, StorageClass>::convert_to_str(std::uint32_t /*field_pos*/, S /*src*/, char*& dst)
	{
		dst = new char[1];
		*dst = 0;
	}


	template<class DerivedLoader, class StorageClass>
	template<class D>
	void SQLStorageLoaderBase<DerivedLoader, StorageClass>::convert_from_str(std::uint32_t /*field_pos*/, char const* /*src*/, D& dst)
	{
		dst = 0;
	}


	template<class DerivedLoader, class StorageClass>
	template<class S, class D>
	void SQLStorageLoaderBase<DerivedLoader, StorageClass>::default_fill(std::uint32_t /*field_pos*/, S src, D& dst)
	{
		dst = D(src);
	}


	template<class DerivedLoader, class StorageClass>
	void SQLStorageLoaderBase<DerivedLoader, StorageClass>::default_fill_to_str(std::uint32_t /*field_pos*/, char const* /*src*/, char*& dst)
	{
		dst = new char[1];
		*dst = 0;
	}


	template<class DerivedLoader, class StorageClass>
	template<class V>
	void SQLStorageLoaderBase<DerivedLoader, StorageClass>::StoreValue(V value, StorageClass& store, char* p, std::uint32_t x, std::uint32_t& offset)
	{
		DerivedLoader* subclass = (static_cast<DerivedLoader*>(this));
		switch (store.GetDstFormat(x))
		{
		case Ucop::DBC::DBC_FF_LOGIC:
			subclass->convert(x, value, *((bool*)(&p[offset])));
			offset += sizeof(bool);
			break;
		case Ucop::DBC::DBC_FF_BYTE:
			subclass->convert(x, value, *((char*)(&p[offset])));
			offset += sizeof(char);
			break;
		case Ucop::DBC::DBC_FF_INT:
			subclass->convert(x, value, *((std::uint32_t*)(&p[offset])));
			offset += sizeof(std::uint32_t);
			break;
		case Ucop::DBC::DBC_FF_FLOAT:
			subclass->convert(x, value, *((float*)(&p[offset])));
			offset += sizeof(float);
			break;
		case Ucop::DBC::DBC_FF_STRING:
			subclass->convert_to_str(x, value, *((char**)(&p[offset])));
			offset += sizeof(char*);
			break;
		case Ucop::DBC::DBC_FF_NA:
			subclass->default_fill(x, value, *((std::uint32_t*)(&p[offset])));
			offset += sizeof(std::uint32_t);
			break;
		case Ucop::DBC::DBC_FF_NA_BYTE:
			subclass->default_fill(x, value, *((char*)(&p[offset])));
			offset += sizeof(char);
			break;
		case Ucop::DBC::DBC_FF_NA_FLOAT:
			subclass->default_fill(x, value, *((float*)(&p[offset])));
			offset += sizeof(float);
			break;
		case Ucop::DBC::DBC_FF_IND:
		case Ucop::DBC::DBC_FF_SORT:
			UCOP_ASSERT(false && "SQL storage does not have sort field types");
			break;
		default:
			UCOP_ASSERT(false && "unknown format character");
			break;
		}
	}


	template<class DerivedLoader, class StorageClass>
	void SQLStorageLoaderBase<DerivedLoader, StorageClass>::StoreValue(char const* value, StorageClass& store, char* p, std::uint32_t x, std::uint32_t& offset)
	{
		DerivedLoader* subclass = (static_cast<DerivedLoader*>(this));
		switch (store.GetDstFormat(x))
		{
		case Ucop::DBC::DBC_FF_LOGIC:
			subclass->convert_from_str(x, value, *((bool*)(&p[offset])));
			offset += sizeof(bool);
			break;
		case Ucop::DBC::DBC_FF_BYTE:
			subclass->convert_from_str(x, value, *((char*)(&p[offset])));
			offset += sizeof(char);
			break;
		case Ucop::DBC::DBC_FF_INT:
			subclass->convert_from_str(x, value, *((std::uint32_t*)(&p[offset])));
			offset += sizeof(std::uint32_t);
			break;
		case Ucop::DBC::DBC_FF_FLOAT:
			subclass->convert_from_str(x, value, *((float*)(&p[offset])));
			offset += sizeof(float);
			break;
		case Ucop::DBC::DBC_FF_STRING:
			subclass->convert_str_to_str(x, value, *((char**)(&p[offset])));
			offset += sizeof(char*);
			break;
		case Ucop::DBC::DBC_FF_NA_POINTER:
			subclass->default_fill_to_str(x, value, *((char**)(&p[offset])));
			offset += sizeof(char*);
			break;
		case Ucop::DBC::DBC_FF_IND:
		case Ucop::DBC::DBC_FF_SORT:
			UCOP_ASSERT(false && "SQL storage does not have sort field types");
			break;
		default:
			UCOP_ASSERT(false && "unknown format character");
			break;
		}
	}


	///////////////////////////////////////////////////////////////////////////
	// Load
	template<class DerivedLoader, class StorageClass>
	template<typename BarType>
	bool SQLStorageLoaderBase<DerivedLoader, StorageClass>::Load(Session &_Sess, StorageClass& _Storage, bool _ErrorAtEmpty /* = true */, void(*_ErrorHandler)(const char*, ...) /* = NULL */)
	{
		Field* _Fields = NULL;
		QueryResultPtr _Result(_Sess.PQuery("SELECT MAX(%s) FROM %s", _Storage.EntryFieldName(), _Storage.GetTableName()));
		if (!_Result.get()) {
			_ErrorHandler("Error loading %s table (not exist?)\n", _Storage.GetTableName());
			return false;
		}

		std::uint32_t _MaxRecordId = (*_Result)[0].AsUInt32() + 1;
		std::uint32_t _RecordCount = 0;
		std::uint32_t _Recordsize = 0;

		_Result.reset(_Sess.PQuery("SELECT COUNT(*) FROM %s", _Storage.GetTableName()));
		if (_Result.get())
		{
			_Fields = _Result->Fetch();
			_RecordCount = _Fields[0].AsUInt32();
		}

		_Result.reset(_Sess.PQuery("SELECT * FROM %s", _Storage.GetTableName()));

		if (!_Result.get())
		{
			_ErrorHandler("%s table is empty!\n", _Storage.GetTableName());
			_RecordCount = 0;
			return _ErrorAtEmpty;
		}

		if (_Storage.GetSrcFieldCount() != _Result->GetFieldCount())
		{
			_RecordCount = 0;
			_ErrorHandler("Error in %s table.Perhaps the table structure was changed. There should be %d fields in the table.\n", _Storage.GetTableName(), _Storage.GetSrcFieldCount());
			return false;
		}

		// get struct size
		std::uint32_t _Offset = 0;
		for (std::uint32_t x = 0; x < _Storage.GetDstFieldCount(); ++x)
		{
			switch (_Storage.GetDstFormat(x))
			{
			case DBC::DBC_FF_LOGIC:
				_Recordsize += sizeof(bool);   break;
			case DBC::DBC_FF_BYTE:
				_Recordsize += sizeof(char);   break;
			case DBC::DBC_FF_INT:
				_Recordsize += sizeof(std::uint32_t); break;
			case DBC::DBC_FF_FLOAT:
				_Recordsize += sizeof(float);  break;
			case DBC::DBC_FF_STRING:
				_Recordsize += sizeof(char*);  break;
			case DBC::DBC_FF_NA:
				_Recordsize += sizeof(std::uint32_t); break;
			case DBC::DBC_FF_NA_BYTE:
				_Recordsize += sizeof(char);   break;
			case DBC::DBC_FF_NA_FLOAT:
				_Recordsize += sizeof(float);  break;
			case DBC::DBC_FF_NA_POINTER:
				_Recordsize += sizeof(char*);  break;
			case DBC::DBC_FF_IND:
			case DBC::DBC_FF_SORT:
				UCOP_ASSERT(false && "SQL storage not have sort field types");
				break;
			default:
				UCOP_ASSERT(false && "unknown format character");
				break;
			}
		}

		// Prepare data storage and lookup storage
		_Storage.PrepareToLoad(_MaxRecordId, _RecordCount, _Recordsize);

		BarType _Bar(_RecordCount);
		do
		{
			_Fields = _Result->Fetch();
			_Bar.Step();

			char* _Record = _Storage.CreateRecord(_Fields[0].AsUInt32());
			_Offset = 0;

			// dependend on dest-size
			// iterate two indexes: x over dest, y over source
			//                      y++ If and only If x != FT_NA*
			//                      x++ If and only If a value is stored
			for (std::uint32_t x = 0, y = 0; x < _Storage.GetDstFieldCount();)
			{
				switch (_Storage.GetDstFormat(x))
				{
					// For default fill continue and do not increase y
				case DBC::DBC_FF_NA:         StoreValue((std::uint32_t)0, _Storage, _Record, x, _Offset);         ++x; continue;
				case DBC::DBC_FF_NA_BYTE:    StoreValue((char)0, _Storage, _Record, x, _Offset);           ++x; continue;
				case DBC::DBC_FF_NA_FLOAT:   StoreValue((float)0.0f, _Storage, _Record, x, _Offset);       ++x; continue;
				case DBC::DBC_FF_NA_POINTER: StoreValue((char const*)NULL, _Storage, _Record, x, _Offset); ++x; continue;
				default:
					break;
				}

				// It is required that the input has at least as many columns set as the output requires
				if (y >= _Storage.GetSrcFieldCount())
				{
					UCOP_ASSERT(false && "SQL storage has too few columns!");
				}

				switch (_Storage.GetSrcFormat(y))
				{
				case DBC::DBC_FF_LOGIC:  StoreValue((bool)(_Fields[y].AsUInt32() > 0), _Storage, _Record, x, _Offset);  ++x; break;
				case DBC::DBC_FF_BYTE:   StoreValue((char)_Fields[y].AsUInt8(), _Storage, _Record, x, _Offset);         ++x; break;
				case DBC::DBC_FF_INT:    StoreValue((std::uint32_t)_Fields[y].AsUInt32(), _Storage, _Record, x, _Offset);      ++x; break;
				case DBC::DBC_FF_FLOAT:  StoreValue((float)_Fields[y].AsFloat(), _Storage, _Record, x, _Offset);        ++x; break;
				case DBC::DBC_FF_STRING: StoreValue((char const*)_Fields[y].c_str(), _Storage, _Record, x, _Offset); ++x; break;
				case DBC::DBC_FF_NA:
				case DBC::DBC_FF_NA_BYTE:
				case DBC::DBC_FF_NA_FLOAT:
					// Do Not increase x
					break;
				case DBC::DBC_FF_IND:
				case DBC::DBC_FF_SORT:
				case DBC::DBC_FF_NA_POINTER:
					UCOP_ASSERT(false && "SQL storage not have sort or pointer field types");
					break;
				default:
					UCOP_ASSERT(false && "unknown format character");
				}
				++y;
			}
		} while (_Result->NextRow());

		return true;
	}

}}

#endif /* DEPENDENCY_UCOP_STORAGE_SQLSTORAGEIMPL_H_ */
